/*
 * Java Card File Parser 
 * Based on openjcvm project 
 */
package org.naive.metawatch.converter.parser;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.text.ParseException;

/**
 *
 * @author Andrew Butenko
 */
public abstract class Parseable {

    public void parse(ParseContext px) throws ParseException {
        final Class<?> clz = getClass();
        try {
            for (Field f : clz.getFields()) {
                Object value = parseField(f, px);
                if (value != null) {
                    f.set(this, value);
                }
            }
        } catch (IllegalAccessException | IllegalAccessError e) {
            ParseException pe = new ParseException("Malconfiguration:", px.getOffset());
            pe.initCause(e);
            throw pe;
        }
    }

    private Class<?> findFieldParser(Annotation a) {
        for (Class<?> ic : a.annotationType().getDeclaredClasses()) {
            if ("FieldParser".equals(ic.getSimpleName())) {
                return ic;
            }
        }
        return null;
    }

    private Object parseField(Field f, ParseContext px) throws ParseException {
        Class<?> fieldParser = null;
        Annotation parserAnnotation = null;
        for (Annotation a : f.getDeclaredAnnotations()) {
            fieldParser = findFieldParser(a);
            if (fieldParser != null) {
                parserAnnotation = a;
                break;
            }
        }
        if (fieldParser != null) {
            try {
                Method m = fieldParser.getDeclaredMethod("parse",
                        parserAnnotation.annotationType(),
                        ParseContext.class,
                        Field.class);
                return m.invoke(fieldParser, parserAnnotation, px, f);
            } catch (IllegalAccessException | IllegalArgumentException | NoSuchMethodException | InvocationTargetException | SecurityException e) {
                ParseException pe = new ParseException("Malconfiguration:", px.getOffset());
                pe.initCause(e);
                throw pe;
            }
        }
        return null;
    }

    public String toString(String indent, NameResolver r) {
        final StringBuffer sb = new StringBuffer();
        try {
            //sb.append(indent);
            sb.append(getClass().getSimpleName());
            sb.append("\n");

            for (Field f : getClass().getDeclaredFields()) {
                if ((f.getModifiers() & Modifier.PUBLIC) == 0) {
                    continue;
                }
                sb.append(indent);
                final Class<?> ft = f.getType();
                Object value = f.get(this);
                if (value != null) {
                    final boolean useResolver = f.getAnnotation(ReferencesConstant.class) != null;
                    sb.append(f.getName());
                    sb.append(":");
                    if (ft.isArray()) {

                        sb.append("\n");
                        sb.append(indent);
                        sb.append("[");
                        for (int i = 0; i < Array.getLength(value); i++) {
                            append(sb, r, Array.get(value, i), indent, useResolver);
                            sb.append("\n");
                            sb.append(indent);
                        }
                        sb.append("]");
                    } else {
                        append(sb, r, value, indent, useResolver);
                    }
                    sb.append("\n");
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

    private void append(StringBuffer sb, NameResolver r, Object v, String indent,
            boolean useResolver) throws IllegalArgumentException, IllegalAccessException {
        Number num = null;
        if (int.class.isAssignableFrom(v.getClass())) {
            num = (int) v;
        }
        if (Number.class.isAssignableFrom(v.getClass())) {
            num = (Number) v;
        }

        if (num != null) {
            if (useResolver && r != null) {
                sb.append(r.getName(num.intValue()));
            } else {
                sb.append(v);
            }
        } else if (Parseable.class.isAssignableFrom(v.getClass())) {
            sb.append(((Parseable) v).toString(indent + "\t", r));
        } else {
            sb.append(v);
        }
    }

    protected int u(byte v) {
        return v & 0xFF;
    }

    protected int u(short v) {
        return v & 0xFFFF;
    }

    @Override
    public String toString() {
        return toString("\t", null);
    }
}
